/**
 * 点，用于将地图点转为点对象数组
 * @class
 */
class Spot {
    /**
     * @param {Number} x - x坐标
     * @param {Number} y - y坐标
     */
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.F = 0; // G + H
        this.G = 0; // G 起点到该点所需距离
        this.H = 0; // H 该点到终点距离
        this.parent = undefined;
        this.neighbors = [];
        this.isGo = true; // 可通行吗
    }

    /**
     * 获取周围可通行点
     * @params {Number} rows - 行数
     * @params {Number} cols - 列数
     * @param {Array} grid 保存地图所有坐标点的Spot实例的数组
     * @param {Boolean} oblique - 是否允许斜线寻路,默认允许
     */
    addNeighbors(rows, cols, grid, oblique) {
        let x = this.x;
        let y = this.y;
        if (y - 1 >= 0) {
            this.neighbors.push(grid[y - 1][x]);
        }
        if (x - 1 >= 0) {
            this.neighbors.push(grid[y][x - 1]);
        }
        if (x + 1 < cols) {
            this.neighbors.push(grid[y][x + 1]);
        }
        if (y + 1 < rows) {
            this.neighbors.push(grid[y + 1][x]);
        }
        if (oblique) {
            if (x - 1 >= 0 && y - 1 >= 0) {
                this.neighbors.push(grid[y - 1][x - 1]);
            }
            if (x + 1 < cols && y - 1 >= 0) {
                this.neighbors.push(grid[y - 1][x + 1]);
            }
            if (x - 1 >= 0 && y + 1 < rows) {
                this.neighbors.push(grid[y + 1][x - 1]);
            }
            if (x + 1 < cols && y + 1 < rows) {
                this.neighbors.push(grid[y + 1][x + 1]);
            }
        }
    }
}

/**
 * a星寻路算法
 * @class
 */
class Astart {
    /**
     * @constructor
     * @params {Number} rows - 行数
     * @params {Number} cols - 列数
     * @param {Boolean} oblique - 是否允许斜线寻路,默认允许
     */
    constructor(rows, cols, oblique = true) {
        this.rows = rows;
        this.cols = cols;
        this.startPos = '';
        this.endPos = '';
        this.openList = []; // 开启列表
        this.closedList = []; // 关闭列表
        this.grid = []; // 二维数组，存储将地图所有点标记为Spot的实例
        this.oblique = oblique;
        this._init(this.oblique);
    }

    /**
     * 初始化
     * @param {Boolean} oblique - 是否允许斜线寻路,默认允许
     */
    _init(oblique) {
        for (let y = 0; y < this.rows; y++) {
            this.grid[y] = [];
            for (let x = 0; x < this.cols; x++) {
                this.grid[y][x] = new Spot(x, y);
            }
        }

        for (let y = 0; y < this.rows; y++) {
            for (let x = 0; x < this.cols; x++) {
                this.grid[y][x].addNeighbors(this.rows, this.cols, this.grid, oblique);
            }
        }
    }

    /**
     * 获取G值
     * @param {Spot} curr - 当前点
     * @param {Spot} next - 下一个点
     */
    _getG(curr, next) {
        let g = 0;
        let x = Math.abs(next.x - curr.x);
        let y = Math.abs(next.y - curr.y);
        if (x == y) {
            g = 14 * x;
        } else {
            g = 10 * (x + y);
        }
        return g;
    }

    /**
     * 获取H值
     * @param {Spot} el - 点对象 
     */
    _getH(el) {
        return (Math.abs(this.startPos.x - el.x) + Math.abs(this.startPos.y - el.y)) * 10;
    }

    /**
     * 获取F值
     * @param {Spot} el - 点对象 
     */
    _getF(el) {
        return el.G + el.H;
    }

    /**
     * 开始寻找路线
     * @return {Spot} spot - 选找到的路线的点
     */
    _run() {
        if (this.openList.length > 0) {
            let currIndex = 0;
            let curr = null;
            for (let i = 0; i < this.openList[i]; i++) {
                if (this.openList[i].F < this.openList[currIndex].F) {
                    currIndex = i;
                }
            }

            curr = this.openList[currIndex];
            if (curr == this.startPos) {
                return curr;
            }

            this.openList.splice(currIndex, 1);
            this.closedList.push(curr);

            for (let i = 0; i < curr.neighbors.length; i++) {
                let neighbor = curr.neighbors[i];

                if (!this.closedList.includes(neighbor) && neighbor.isGo) {
                    let newPath = false;
                    let tmpF = curr.G + this._getG(curr, neighbor) + this._getH(neighbor);
                    if (this.openList.includes(neighbor)) {
                        if (tmpF < neighbor.F) {
                            neighbor.F = tmpF;
                            newPath = true;
                        }
                    } else {
                        neighbor.G = this._getG(curr, neighbor) + curr.G;
                        neighbor.H = this._getH(neighbor);
                        neighbor.F = this._getF(neighbor);
                        newPath = true;
                        this.openList.push(neighbor);
                    }
                    if (newPath) {
                        neighbor.parent = curr;
                    }
                }
            }

            this._run();
            // return this._run();

        } else {
            return false;
        }
    }

    /**返回grid */
    get grids() {
        return this.grid;
    }

    /**
     * @param {Object} start - 起点坐标对象
     * @param {Object} end - 终点坐标对象
     * @return {Spot} spot - 选找到的路线的点
     */
    selectSpot(start, end) {
        this.openList = [];
        this.closedList = [];
        this.startPos = this.grid[start.y][start.x];
        this.endPos = this.grid[end.y][end.x];
        this.endPos.G = 0;
        this.endPos.H = this._getH(this.endPos);
        this.endPos.F = this._getF(this.endPos);
        this.startPos.isGo = this.endPos.isGo = true;
        this.openList.push(this.endPos);
        let spot = this._run();
        return spot;
    }

    /**
    * 设置寻路方式
    * @param {Boolean} oblique - 是否允许斜线寻路,默认允许
    * @example 允许链式调用
    */
    setMode(oblique) {
        this._init(oblique);
        return this;
    }

}